package com.PDP.service;

import com.PDP.DTO.AWPAndEmployeeDTO;
import com.PDP.DTO.PDProcessDTO;
import com.PDP.model.*;
import com.PDP.repository.BaseRepository;
import com.PDP.security.audit.AuditRepository;
import com.PDP.security.user.UserEntity;
import com.PDP.util.reports.*;
import com.PDP.util.reports.customReports.CustomReportEntity;
import com.PDP.util.reports.customReports.QueryData;
import com.PDP.util.reports.customReports.ReportRepository;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;


@Service
@Transactional
public class PDProcessService extends AuthCheckingService<PDProcess> {
    @Autowired
    private Environment env;
    @Autowired
    private final PDTargetService pdTargetService;
    @Autowired
    private final PDSubjectService pdSubjectService;

    @Autowired
    private final EmployeeService employeeService;
    @Autowired
    private final PDRegDocService pdRegDocService;
    @Autowired
    private final PDProcessTypeService pdProcessTypeService;

    @Autowired
    private final AWPService awpService;

    @Autowired
    private final PDDocumentService pdDocumentService;

    @Autowired
    private final PDTypeService pdTypeService;

    @Autowired
    private final ISPDNService ispdnService;

    @Autowired
    private final ReportRepository reportRepository;

    @Autowired
    private final PDProcessActionService pdProcessActionService;

    @Autowired
    private final DeleteConditionService deleteConditionService;

    public PDProcessService(BaseRepository<PDProcess, Long> repository, AuditRepository auditRepository, Environment env, PDTargetService pdTargetService, PDSubjectService pdSubjectService, EmployeeService employeeService, PDRegDocService pdRegDocService, PDProcessTypeService pdProcessTypeService, AWPService awpService, PDDocumentService pdDocumentService, PDTypeService pdTypeService, ISPDNService ispdnService, ReportRepository reportRepository, PDProcessActionService pdProcessActionService, DeleteConditionService deleteConditionService) {
        super(repository, auditRepository);
        this.env = env;
        this.pdTargetService = pdTargetService;
        this.pdSubjectService = pdSubjectService;
        this.employeeService = employeeService;
        this.pdRegDocService = pdRegDocService;
        this.pdProcessTypeService = pdProcessTypeService;
        this.awpService = awpService;
        this.pdDocumentService = pdDocumentService;
        this.pdTypeService = pdTypeService;
        this.ispdnService = ispdnService;
        this.reportRepository = reportRepository;
        this.pdProcessActionService = pdProcessActionService;
        this.deleteConditionService = deleteConditionService;
    }

    private void createMissingEntities(PDProcess pdProcess) {
        if (pdProcess.getPdSubjects() != null) {
            ArrayList<PDSubject> newArr = new ArrayList<>(pdProcess.getPdSubjects().size());
            for (PDSubject pds : pdProcess.getPdSubjects()) {
                newArr.add(pdSubjectService.findByNameOrCreate(pds));
            }
            pdProcess.setPdSubjects(newArr);
        }
        if (pdProcess.getPdProcessActions() != null) {
            ArrayList<PDProcessAction> newArr = new ArrayList<>(pdProcess.getPdProcessActions().size());
            for (PDProcessAction pda : pdProcess.getPdProcessActions()) {
                newArr.add(pdProcessActionService.findByNameOrCreate(pda));
            }
            pdProcess.setPdProcessActions(newArr);
        }
        if (pdProcess.getPdType() != null) {
            ArrayList<PDType> newArr = new ArrayList<>(pdProcess.getPdType().size());
            for (PDType pda : pdProcess.getPdType()) {
                newArr.add(pdTypeService.findByNameOrCreate(pda));
            }
            pdProcess.setPdType(newArr);
        }
        pdProcess.setPdTargets(pdTargetService.findByNameOrCreate(pdProcess.getPdTargets()));
        pdProcess.setPdRegDoc(pdRegDocService.findByNameOrCreate(pdProcess.getPdRegDoc()));
        pdProcess.setEmployee(employeeService.findByNameOrCreate(pdProcess.getEmployee()));
        pdProcess.setPdProcessType(pdProcessTypeService.findByNameOrCreate(pdProcess.getPdProcessType()));
        pdProcess.setPdStorage(awpService.findByNameOrCreate(pdProcess.getPdStorage()));
        pdProcess.setPdDocument(pdDocumentService.findByNameOrCreate(pdProcess.getPdDocument()));
        pdProcess.setPdProcessPlace(awpService.findByNameOrCreate(pdProcess.getPdProcessPlace()));
        //pdProcess.setIspdn(ispdnService.findByNameOrCreate(pdProcess.getIspdn()));
        pdProcess.setDeleteCondition(deleteConditionService.findByNameOrCreate(pdProcess.getDeleteCondition()));


    }

    public Iterable<PDProcess> getAllPDProcesses(Authentication auth) {
        UserEntity user = (UserEntity) auth.getPrincipal();
        if (user.getAllSubdivisions())
            return repository.findAll();

        return repository.findAll().stream().filter(pdProcess -> {
            return pdProcess.getSubdivisionName().equals(user.getSubdivision());
        }).toList();
    }

    public HttpStatus createIfNotExists(PDProcess pdProcess) {

        if (pdProcess.getId() != null)
            if (repository.existsById(pdProcess.getId())) {
                return HttpStatus.OK;
            }
        createMissingEntities(pdProcess);
        repository.saveAndFlush(pdProcess);
        addAuditionEntity(pdProcess,"created");
        return HttpStatus.CREATED;
    }

    public HttpStatus editPDProcess(Authentication auth, PDProcess pdProcess, Long id) {
        UserEntity user = (UserEntity) auth.getPrincipal();
        Optional<PDProcess> old = repository.findById(id);
        createMissingEntities(pdProcess);
        if (old.isEmpty()) {
            if (checkAuth(user, pdProcess)) {
                repository.saveAndFlush(pdProcess);
                addAuditionEntity(pdProcess,"created");
                return HttpStatus.CREATED;
            }
            return HttpStatus.FORBIDDEN;
        }
        if (checkAuth(user, old.get())) {
            pdProcess.setId(old.get().getId());
            addAuditionEntity(pdProcess,"edited");
            repository.saveAndFlush(pdProcess);
            return HttpStatus.OK;
        }
        return HttpStatus.FORBIDDEN;

    }


//    public HashMap<Long, ISPDNReport> getISPDNsInfo(Authentication auth) {
//        HashMap<Long, ISPDNReport> reports = new HashMap<>();
//        for (PDProcess pdProcess : getAllPDProcesses(auth)) {
//            ISPDNReport tmp = reports.get(pdProcess.getIspdn().getId());
//            if (tmp == null) {
//                tmp = new ISPDNReport();
//            }
//            tmp.setIspdnLocation(pdProcess.getIspdn().getLocation());
//            tmp.setIspdnTransBorder(pdProcess.getIspdn().getOverBorder());
//            tmp.getPdSubjects().addAll(pdProcess.getPdSubjects());
//            tmp.getPdProcessTypes().add(pdProcess.getPdProcessType());
//            //tmp.getPdType().add(pdProcess.getPdType());
//            tmp.getPdProcessActions().addAll(pdProcess.getPdProcessActions());
//            reports.put(pdProcess.getIspdn().getId(), tmp);
//
//        }
//        return reports;
//
//    }

    //TODO: перекинуть все отчеты в отдельный класс
    public AWPReport getAWPReport(String subdivision) {
        List<AWPAndEmployeeDTO> awpAndEmployeeDTOS = new ArrayList<>();
        //TODO: сделать метод в репозитории поиск по подразделению
        for (Employee e : employeeService.getAll().stream().filter(employee -> employee.getSubdivisionName().equals(subdivision)).toList()) {
            awpAndEmployeeDTOS.add(new AWPAndEmployeeDTO(e));
        }
        return new AWPReport(subdivision, awpAndEmployeeDTOS);
    }

    public EmployeeReport getEmployeeReport() {
        List<AWPAndEmployeeDTO> awpAndEmployeeDTOS = new ArrayList<>();
        for (Employee e : employeeService.getAll()) {
            awpAndEmployeeDTOS.add(new AWPAndEmployeeDTO(e));
        }
        return new EmployeeReport(awpAndEmployeeDTOS);
    }

    public PdDocumentReport getDocumentReport() {
        List<PDProcessDTO> pdProcessDTOS = new ArrayList<>();
        for (PDProcess pdProcess : repository.findAll()) {
            pdProcessDTOS.add(new PDProcessDTO(pdProcess));
        }
        return new PdDocumentReport(pdProcessDTOS);
    }

    public List<ReportInfo> getReportsInfo() {
        ArrayList<ReportInfo> reportInfos = new ArrayList<>();
        reportInfos.add(ICOPDReport.getReportInfo());
        reportInfos.add(AWPReport.getReportInfo());
        reportInfos.add(EmployeeReport.getReportInfo());
        reportInfos.add(PdDocumentReport.getReportInfo());
        reportRepository.findAll().forEach(e -> {
            e.getReportInfo().setAddress("/PDP/reports/getReport/" + e.getId().toString());
            reportInfos.add(e.getReportInfo());
        });
        return reportInfos;
    }

    public List<PDProcess> getReportById(Long id, HashMap<String, String> props) {
        CustomReportEntity reportEntity = reportRepository.getReferenceById(id);
        List<PDProcess> pdProcesses = repository.findAll();


        for (QueryData qd : reportEntity.getQuery()) {
            switch (qd.getEntity()) {
                case "process": {
                    pdProcesses = pdProcesses.stream().filter(e -> {
                        boolean res = true;
                        for (Map.Entry<String, String> entry : props.entrySet()) {
                            String key = entry.getKey();
                            String value = entry.getValue();
                            if (key.startsWith(qd.getEntity())) {
                                try {
                                    Object o = e.getClass().getField(key.replace(qd.getEntity() + "_", "")).get(e);
                                    if (o instanceof List<?>) {
                                        AtomicBoolean p = new AtomicBoolean(false);
                                        ((List<?>) o).forEach(el -> {
                                            p.set(p.get() | el.toString().equals(value));
                                        });
                                        res &= p.get();
                                    } else
                                        res &= o.toString().equals(value);
                                } catch (IllegalAccessException | NoSuchFieldException ex) {
                                    throw new RuntimeException(ex);
                                }
                            }
                        }
                        return res;
                    }).toList();
                }
                case "employee": {
                    pdProcesses = pdProcesses.stream().filter(b -> {
                        Employee e = b.getEmployee();
                        if (e == null)
                            return false;
                        boolean res = true;
                        for (Map.Entry<String, String> entry : props.entrySet()) {
                            String key = entry.getKey();
                            String value = entry.getValue();
                            if (key.startsWith(qd.getEntity())) {
                                try {
                                    res &= e.getClass().getField(key.replace(qd.getEntity() + "_", "")).get(e).toString().equals(value);
                                } catch (IllegalAccessException | NoSuchFieldException ex) {
                                    throw new RuntimeException(ex);
                                }
                            }
                        }
                        return res;
                    }).toList();
                }
                case "AWP": {
                    pdProcesses = pdProcesses.stream().filter(b -> {
                        Employee e1 = b.getEmployee();
                        if (e1 == null)
                            return false;
                        AWP e = e1.getAwp();
                        if (e == null)
                            return false;
                        boolean res = true;
                        for (Map.Entry<String, String> entry : props.entrySet()) {
                            String key = entry.getKey();
                            String value = entry.getValue();
                            if (key.startsWith(qd.getEntity())) {
                                try {
                                    res &= e.getClass().getField(key.replace(qd.getEntity() + "_", "")).get(e).toString().equals(value);
                                } catch (IllegalAccessException | NoSuchFieldException ex) {
                                    throw new RuntimeException(ex);
                                }
                            }
                        }
                        return res;
                    }).toList();

                }
                case "ISPDN": {
                    pdProcesses = pdProcesses.stream().filter(b -> {
                        Employee e1 = b.getEmployee();
                        if (e1 == null)
                            return false;
                        AWP e2 = e1.getAwp();
                        if (e2 == null)
                            return false;
                        List<ISPDN> es = e2.getIspdns();
                        AtomicBoolean res = new AtomicBoolean(true);
                        es.forEach(e -> {
                            for (Map.Entry<String, String> entry : props.entrySet()) {
                                String key = entry.getKey();
                                String value = entry.getValue();
                                if (key.startsWith(qd.getEntity())) {
                                    try {
                                        res.set(res.get() | e.getClass().getField(key.replace(qd.getEntity() + "_", "")).get(e).toString().equals(value));
                                    } catch (IllegalAccessException | NoSuchFieldException ex) {
                                        throw new RuntimeException(ex);
                                    }
                                }
                            }
                        });
                        return res.get();
                    }).toList();

                }
            }
        }

        return pdProcesses;
    }

    public void createReport(CustomReportEntity customReportEntity) {
        reportRepository.save(customReportEntity);
    }

    public void deleteReport(Long id) {
        reportRepository.deleteById(id);
    }

    public ICOPDReport getICOPDReport(String subdivision) {
        List<PDProcess> pdProcesses= repository.findAll().stream().filter(pdProcess -> pdProcess.getSubdivisionName().equals(subdivision)).toList();
        List<AWPAndEmployeeDTO> awpAndEmployeeDTOS= new ArrayList<>();
        //TODO: сделать метод в репозитории поиск по подразделению
        for(Employee e:employeeService.getAll().stream().filter(employee -> employee.getSubdivisionName().equals(subdivision)).toList()){
            awpAndEmployeeDTOS.add(new AWPAndEmployeeDTO(e));
        }
        return new ICOPDReport(subdivision,pdProcesses,awpAndEmployeeDTOS);
    }
}
